package com.zjun.demo.experimentaldemo.udp.client

import com.zjun.demo.experimentaldemo.udp.call.HOTSPOT_HOST_PORT
import com.zjun.demo.experimentaldemo.udp.call.IChannelFutureCallBack
import com.zjun.demo.experimentaldemo.udp.call.ICallBackMsg
import com.zjun.demo.experimentaldemo.udp.call.IClientLaunchCallBack
import com.zjun.demo.experimentaldemo.udp.call.NettyException
import io.netty.bootstrap.Bootstrap
import io.netty.buffer.Unpooled
import io.netty.channel.Channel
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInitializer
import io.netty.channel.ChannelOption
import io.netty.channel.EventLoopGroup
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.DatagramPacket
import io.netty.channel.socket.nio.NioDatagramChannel
import io.netty.util.CharsetUtil
import java.net.BindException
import java.net.InetSocketAddress
import kotlin.random.Random

class UdpClient private constructor() {


    private var bootstrap: Bootstrap? = null

    private var worker: EventLoopGroup? = null

    private var channel: Channel? = null

    private var isConnect: Boolean = false

    private var isConnecting: Boolean = false

    private var iCallBackMsg: ICallBackMsg? = null

    private var iIClientLaunchCallBack: IClientLaunchCallBack? = null

    fun setCallBackMsgListener(iCallBackMsg: ICallBackMsg){
        if(bootstrap !=null){
            throw Exception("需要在初始化前设置")
        }
        this.iCallBackMsg = iCallBackMsg
    }

    fun setClientLaunchCallBack(iIClientLaunchCallBack: IClientLaunchCallBack){
        if(bootstrap !=null){
            throw Exception("需要在初始化前设置")
        }
        this.iIClientLaunchCallBack = iIClientLaunchCallBack
    }

    fun start() {
        try {
            isConnecting = true
            if (isConnect) {
                return
            }
            stop()
            worker = NioEventLoopGroup()
            bootstrap = Bootstrap()
                .group(worker)
                .channel(NioDatagramChannel::class.java)
                .option(ChannelOption.SO_BROADCAST, true)
                .handler(object : ChannelInitializer<Channel>() {

                    @Throws(Exception::class)
                    override fun initChannel(ch: Channel) {
                        isConnect = true
                        isConnecting = false
                        ch.pipeline().addLast(UdpClientHandler(iCallBackMsg))
                        iIClientLaunchCallBack?.clientLaunchSuccess(ch)
                    }

                    override fun channelInactive(ctx: ChannelHandlerContext) {
                        super.channelInactive(ctx)
                        isConnect = false
                        isConnecting = false
                        iIClientLaunchCallBack?.clientLaunchFail(ctx)
                    }

                })
            //端口范围：0~65535
            val port = Random.nextInt(0, 65535 + 1)
            channel = bootstrap!!.bind(port).sync().channel()
        } catch (e: Exception) {
            isConnect = false
            isConnecting = false
        } catch (e: BindException) {
            isConnect = false
            isConnecting = false
            start()
        }
    }

    fun sendMsg(sendMessage: String, futureListener: IChannelFutureCallBack? = null) {
        if (!isConnect) {
            futureListener?.fail(NettyException("链接尚未建立", NettyException.NETTY_NOT_CONNECT))
            return
        }
        if (channel == null) {
            futureListener?.fail(NettyException("通道未初始化", NettyException.CHANNEL_NOT_INITIALIZED))
            return
        }
        if (!channel!!.isWritable) {
            futureListener?.fail(NettyException("通道不可写", NettyException.CHANNEL_NOT_WRITABLE))
            return
        }
        if (!channel!!.isActive) {
            futureListener?.fail(NettyException("通道未激活", NettyException.CHANNEL_NOT_ACTIVE))
            return
        }
        channel?.writeAndFlush(
            DatagramPacket(
                Unpooled.copiedBuffer(sendMessage, CharsetUtil.UTF_8),
                InetSocketAddress(HOTSPOT_HOST_PORT)
            )
        )
    }

    fun stop() {
        if (channel != null) {
            channel!!.disconnect()
            channel!!.close()
            channel = null
        }
        if (worker != null) {
            worker!!.shutdownGracefully()
            worker = null
        }
        bootstrap = null
    }

    fun destroy(){
        isConnecting = false
        isConnect = false
        iCallBackMsg = null
        iIClientLaunchCallBack = null
        stop()
    }

    companion object {

        val TAG = UdpClient::class.java.simpleName

        val instance = UdpClient()

    }

}